use std::sync::Arc;

use entity::{blocked_crates, unsync_crates};
use itertools::Itertools;
use sea_orm::entity::ModelTrait;
use sea_orm::sea_query::OnConflict;
use sea_orm::ActiveValue::NotSet;
use sea_orm::{
    ActiveModelTrait, ColumnTrait, DatabaseConnection, DeleteResult, EntityTrait, IntoActiveModel,
    QueryFilter, Set,
};

use crate::database::init::database_connection;
use crate::errors::FreightResult;
use crate::{config::DbConfig, errors::FreighterError};

#[derive(Clone)]
pub struct DbService {
    pub connection: Arc<DatabaseConnection>,
}

impl DbService {
    pub async fn new(config: &DbConfig) -> Self {
        Self {
            connection: Arc::new(database_connection(config).await),
        }
    }

    pub fn get_connection(&self) -> &DatabaseConnection {
        &self.connection
    }

    pub async fn save_block_crates(&self, c_name: &str, c_version: &str) -> FreightResult {
        let res = self
            .get_block_by_name_version(c_name, c_version)
            .await
            .unwrap();
        let model = if let Some(res) = res {
            let mut res = res.into_active_model();
            res.blocked = Set(true);
            res
        } else {
            blocked_crates::ActiveModel {
                id: NotSet,
                crate_name: Set(c_name.to_owned()),
                crate_id: NotSet,
                version: Set(c_version.to_owned()),
                blocked: Set(true),
                block_time: Set(chrono::Utc::now().naive_utc()),
            }
        };
        model.save(self.get_connection()).await.unwrap();
        Ok(())
    }

    pub async fn get_block_by_name_version(
        &self,
        c_name: &str,
        c_version: &str,
    ) -> Result<Option<blocked_crates::Model>, FreighterError> {
        let result = blocked_crates::Entity::find()
            .filter(blocked_crates::Column::CrateName.eq(c_name))
            .filter(blocked_crates::Column::Version.eq(c_version))
            .one(self.get_connection())
            .await
            .unwrap();
        Ok(result)
    }

    pub async fn _get_all_block_crates(
        &self,
    ) -> Result<Vec<blocked_crates::Model>, FreighterError> {
        let result = blocked_crates::Entity::find()
            .filter(blocked_crates::Column::Blocked.eq(true))
            .all(self.get_connection())
            .await
            .unwrap();
        Ok(result)
    }

    pub async fn save_unsync_crates_batch(&self, unsync_names: Vec<String>) -> FreightResult {
        let save_models: Vec<unsync_crates::ActiveModel> = unsync_names
            .iter()
            .map(|name| unsync_crates::ActiveModel {
                id: NotSet,
                crate_name: Set(name.to_owned()),
                create_time: Set(chrono::Utc::now().naive_utc()),
            })
            .collect();

        for chunk in &save_models.into_iter().chunks(1000) {
            unsync_crates::Entity::insert_many(chunk)
                .on_conflict(
                    OnConflict::column(unsync_crates::Column::CrateName)
                        .update_column(unsync_crates::Column::CreateTime)
                        .to_owned(),
                )
                .exec(self.get_connection())
                .await
                .unwrap();
        }
        Ok(())
    }

    pub async fn save_unsync_crates(&self, c_name: &str) -> FreightResult {
        let model = unsync_crates::ActiveModel {
            id: NotSet,
            crate_name: Set(c_name.to_owned()),
            create_time: Set(chrono::Utc::now().naive_utc()),
        };
        unsync_crates::Entity::insert(model)
            .on_conflict(
                OnConflict::column(unsync_crates::Column::CrateName)
                    .update_column(unsync_crates::Column::CreateTime)
                    .to_owned(),
            )
            .exec(self.get_connection())
            .await
            .unwrap();
        Ok(())
    }

    pub async fn delete_unsync_record(&self, c_name: &str) -> Result<DeleteResult, FreighterError> {
        let result = unsync_crates::Entity::find()
            .filter(unsync_crates::Column::CrateName.eq(c_name))
            .one(self.get_connection())
            .await
            .unwrap();
        let res = match result {
            Some(model) => model.delete(self.get_connection()).await?,
            None => DeleteResult { rows_affected: 0 },
        };
        Ok(res)
    }

    pub async fn get_all_unsync_crates(&self) -> Result<Vec<unsync_crates::Model>, FreighterError> {
        let result = unsync_crates::Entity::find()
            .all(self.get_connection())
            .await
            .unwrap();
        Ok(result)
    }
}
